using System;
using System.Collections.Generic;
using Fadd.Globalization.Localizations;

namespace Fadd.Globalization
{

	/// <summary>
    /// Used to parse and format date/times using the current language.
    /// </summary>
    /// todo: Copy latest documentation from IDateTimeHelper.
    public static class DateTimeHelper
    {
        private static readonly Dictionary<int, IDateTimeHelper> _helpers = new Dictionary<int, IDateTimeHelper>();

        /// <summary>
        /// Add a parser.
        /// </summary>
        /// <param name="lcid"></param>
        /// <param name="parser"></param>
        /// <returns></returns>
        public static bool Add(int lcid, IDateTimeHelper parser)
        {
            if (_helpers.ContainsKey(lcid))
                return false;

            _helpers.Add(lcid, parser);
            return true;
        }

        /// <summary>
        /// Add default language parsers.
        /// </summary>
        public static void AddDefaultParsers()
        {
            Add(1053, new DateTimeHelper1053()); //swedish
        }
        

        /// <summary>
        /// Will try to parse the date/time using the local parser.
        /// Will also try the default DateTime.Parse method if date is in an unknown format.
        /// </summary>
        /// <param name="value">date/time string</param>
        /// <returns>A <see cref="DateTime"/></returns>
        /// <exception cref="FormatException">If the date/time format is invalid.</exception>
        /// <exception cref="ArgumentOutOfRangeException">If month/day/hour/minute/second are not within their ranges.</exception>
        public static DateTime ParseDateTime(string value)
        {
            return GetHelper().ParseDateTime(value);
        }

        /// <summary>
        /// Will try to parse the date/time using the local parser.
        /// Will also try the default DateTime.Parse method if date is in an unknown format.
        /// </summary>
        /// <param name="date">date string (may be null/empty).</param>
        /// <param name="time">time string (may be null/empty).</param>
        /// <returns>A <see cref="DateTime"/></returns>
        /// <exception cref="FormatException">If the date/time format is invalid.</exception>
        /// <exception cref="ArgumentOutOfRangeException">If month/day/hour/minute/second are not within their ranges.</exception>
        public static DateTime ParseDateTime(string date, string time)
        {
            return GetHelper().ParseDateTime(date, time);
        }

        private static IDateTimeHelper GetHelper()
        {
            IDateTimeHelper parser;
            lock (_helpers)
				if (!_helpers.TryGetValue(CurrentCulture.LCID, out parser))
                    throw new NotSupportedException("No date/time parser for LCID: " + CurrentCulture.LCID);
            return parser;            
        }
        /// <summary>
        /// Tries to parse a date string.
        /// </summary>
        /// <param name="value">Can contain a complete date or parts of it (which parts depends on the country).</param>
        /// <returns>A <see cref="DateTime"/> containing the date (time is 0) if parsed successfully; otherwise <see cref="DateTime.MinValue"/>.</returns>
        public static DateTime ParseDate(string value)
        {
            return GetHelper().ParseDate(value);
        }

        /// <summary>
        /// Tries to parse a date string.
        /// </summary>
        /// <param name="value">Can contain a complete date or parts of it (which parts depends on the country).</param>
        /// <param name="values">array that parsed values are written to.</param>
        /// <param name="offset">where in array to start write values</param>
        /// <returns>true if parsed successfully; otherwise false</returns>
        public static void ParseDate(string value, int[] values, int offset)
        {
            GetHelper().ParseDate(value, values, offset);
        }

        /// <summary>
        /// Tries to parse a time string.
        /// </summary>
        /// <param name="value">Can contain a complete time or parts of it (which parts depends on the country).</param>
        /// <returns>A <see cref="DateTime"/> containing the time (date is 0) if parsed successfully; otherwise <see cref="DateTime.MinValue"/>.</returns>
        public static DateTime ParseTime(string value)
        {
            return GetHelper().ParseTime(value);
        }

        /// <summary>
        /// Tries to parse a time string.
        /// </summary>
        /// <param name="value">Can contain a complete time or parts of it (which parts depends on the country).</param>
        /// <param name="values">array that will be filled. Must contain three slots.</param>
        /// <param name="offset">Where in the array to start write values</param>
        /// <returns>true if parsed successfully; otherwise false.</returns>
        public static void ParseTime(string value, int[] values, int offset)
        {
            GetHelper().ParseTime(value, values, offset);
        }

        /// <summary>
        /// Validate a date.
        /// </summary>
        /// <param name="year">year value</param>
        /// <param name="month">month value</param>
        /// <param name="day">day value</param>
        /// <param name="throwException">true if exceptions should be thrown on incorrect values.</param>
        /// <returns>true if specified values are a correct date; otherwise false.</returns>
        public static bool ValidateDate(int year, int month, int day, bool throwException)
        {
            return GetHelper().ValidateDate(year, month, day, throwException);
        }

        /// <summary>
        /// Validate 
        /// </summary>
        /// <param name="hour">hour</param>
        /// <param name="minute">minute</param>
        /// <param name="second">second</param>
        /// <param name="throwException">true if exceptions should be thrown on incorrect values.</param>
        /// <returns>true if specified values are a correct date; otherwise false.</returns>
        public static bool ValidateTime(int hour, int minute, int second, bool throwException)
        {
            return GetHelper().ValidateTime(hour, minute, second, throwException);
        }

        /// <summary>
        /// Format a date as a string.
        /// </summary>
        /// <param name="dateTime">A date/time</param>
        /// <returns>Examples: "Yesterday", "On monday"</returns>
        public static string FormatDate(DateTime dateTime)
        {
            return GetHelper().FormatDate(dateTime);
        }

        /// <summary>
        /// Format a date/time as a string.
        /// </summary>
        /// <param name="dateTime">A date/time</param>
        /// <returns>Examples: "Yesterday at 12:20pm", "On monday at 11:38am"</returns>
        public static string FormatDateTime(DateTime dateTime)
        {
            return GetHelper().FormatDateTime(dateTime);
        }

        /// <summary>
        /// Get duration as a human friendly string.
        /// </summary>
        /// <param name="from">Start time of duration</param>
        /// <param name="to">End time of duration</param>
        /// <param name="durationDetail">The detail in which to display the duration</param>
        /// <param name="measures">Number of time units to display</param>
        /// <returns>
        /// A string like: 1 month, 2 weeks, and 3 days.
        /// </returns>
        public static string GetDuration(DateTime from, DateTime to, DurationDetail durationDetail, int measures)
        {
            return GetHelper().GetDuration(from, to, durationDetail, measures);
        }

        /// <summary>
        /// Get duration as a human friendly string. Displays all time unit durations.
        /// </summary>
        /// <param name="from">Start time of duration</param>
        /// <param name="to">End time of duration</param>
        public static string GetDuration(DateTime from, DateTime to)
        {
            return GetHelper().GetDuration(from, to);
        }

        /// <summary>
        /// Get duration as a human friendly string.
        /// </summary>
        /// <param name="span">The timespan for which to output the duration</param>
        /// <param name="durationDetail">The detail in which to display the duration</param>
        /// /// <param name="measures">Number of time measures to display</param>
        /// <returns>
        /// A string like: 1 month, 2 weeks, and 3 days.
        /// </returns>
        /// <remarks><see cref="DurationDetail.Months"/> cannot be used when using this method since counting of months
        /// requires both to and from datetime information, in such a case user <see cref="GetDuration(DateTime,DateTime,DurationDetail,int)"/></remarks>
        public static string GetDuration(TimeSpan span, DurationDetail durationDetail, int measures)
        {
            return GetHelper().GetDuration(span, durationDetail, measures);
        }

        /// <summary>
        /// Get duration as a human friendly string. Displays all time unit durations.
        /// </summary>
        /// <param name="span">The timespan for which to output the duration</param>
        public static string GetDuration(TimeSpan span)
        {
            return GetHelper().GetDuration(span);
        }
    }
}
